From 8e27fc7f9b0f34880bdb3650ba141ad65883c094 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Thu, 4 Nov 2021 15:56:37 +0100 Subject: [PATCH] label: Redo measure() code The old code couldn't properly do height-for-width because it only computed the widest and smallest layout instead of looking at the actual passed in for-size. The label-sizing reftest has been adapted as the label code is now smart enough to always display the whole text and no longer requests a too small width-for-single-row when wrapping. --- gtk/gtklabel.c | 278 ++++++++++++++----------- testsuite/reftests/label-sizing.ref.ui | 108 ++++------ 2 files changed, 191 insertions(+), 195 deletions(-) diff --git a/gtk/gtklabel.c b/gtk/gtklabel.c index b9886dd471..3580deae1e 100644 --- a/gtk/gtklabel.c +++ b/gtk/gtklabel.c @@ -1018,8 +1018,7 @@ gtk_label_get_measuring_layout (GtkLabel *self, } static int -get_char_pixels (GtkWidget *self, - PangoLayout *layout) +get_char_pixels (PangoLayout *layout) { PangoContext *context; PangoFontMetrics *metrics; @@ -1035,159 +1034,184 @@ get_char_pixels (GtkWidget *self, } static void -get_height_for_width (GtkLabel *self, - int width, - int *minimum_height, - int *natural_height, - int *minimum_baseline, - int *natural_baseline) +get_default_widths (GtkLabel *self, + int *minimum, + int *natural) { - PangoLayout *layout; - int text_height, baseline; - - layout = gtk_label_get_measuring_layout (self, NULL, width * PANGO_SCALE); + int char_pixels; - pango_layout_get_pixel_size (layout, NULL, &text_height); + if (self->width_chars < 0 && self->max_width_chars < 0) + { + if (minimum) + *minimum = -1; + if (natural) + *natural = -1; + return; + } - *minimum_height = text_height; - *natural_height = text_height; + gtk_label_ensure_layout (self); + char_pixels = get_char_pixels (self->layout); - baseline = pango_layout_get_baseline (layout) / PANGO_SCALE; - *minimum_baseline = baseline; - *natural_baseline = baseline; + if (minimum) + { + if (self->width_chars < 0) + *minimum = -1; + else + *minimum = char_pixels * self->width_chars; + } - g_object_unref (layout); + if (natural) + { + if (self->max_width_chars < 0) + *natural = -1; + else + *natural = char_pixels * MAX (self->width_chars, self->max_width_chars); + } } static void -gtk_label_get_preferred_layout_size (GtkLabel *self, - PangoRectangle *smallest, - PangoRectangle *widest, - int *smallest_baseline, - int *widest_baseline) +get_static_size (GtkLabel *self, + GtkOrientation orientation, + int *minimum, + int *natural, + int *minimum_baseline, + int *natural_baseline) { PangoLayout *layout; - int char_pixels; - /* "width-chars" Hard-coded minimum width: - * - minimum size should be MAX (width-chars, strlen ("...")); - * - natural size should be MAX (width-chars, strlen (self->text)); - * - * "max-width-chars" User specified maximum size requisition - * - minimum size should be MAX (width-chars, 0) - * - natural size should be MIN (max-width-chars, strlen (self->text)) - * - * For ellipsizing labels; if max-width-chars is specified: either it is used as - * a minimum size or the label text as a minimum size (natural size still overflows). - * - * For wrapping labels; A reasonable minimum size is useful to naturally layout - * interfaces automatically. In this case if no "width-chars" is specified, the minimum - * width will default to the wrap guess that gtk_label_ensure_layout() does. - */ - - /* Start off with the pixel extents of an as-wide-as-possible layout */ layout = gtk_label_get_measuring_layout (self, NULL, -1); - if (self->width_chars > -1 || self->max_width_chars > -1) - char_pixels = get_char_pixels (GTK_WIDGET (self), layout); - else - char_pixels = 0; - - pango_layout_get_extents (layout, NULL, widest); - widest->width = MAX (widest->width, char_pixels * self->width_chars); - widest->x = widest->y = 0; - *widest_baseline = pango_layout_get_baseline (layout) / PANGO_SCALE; - - if (self->ellipsize || self->wrap) + if (orientation == GTK_ORIENTATION_HORIZONTAL) { - /* a layout with width 0 will be as small as humanly possible */ - layout = gtk_label_get_measuring_layout (self, - layout, - self->width_chars > -1 ? char_pixels * self->width_chars - : 0); - - pango_layout_get_extents (layout, NULL, smallest); - smallest->width = MAX (smallest->width, char_pixels * self->width_chars); - smallest->x = smallest->y = 0; - - *smallest_baseline = pango_layout_get_baseline (layout) / PANGO_SCALE; + int minimum_default, natural_default; - if (self->max_width_chars > -1 && widest->width > char_pixels * self->max_width_chars) + pango_layout_get_size (layout, natural, NULL); + if (self->ellipsize) { - layout = gtk_label_get_measuring_layout (self, - layout, - MAX (smallest->width, char_pixels * self->max_width_chars)); - pango_layout_get_extents (layout, NULL, widest); - widest->width = MAX (widest->width, char_pixels * self->width_chars); - widest->x = widest->y = 0; - - *widest_baseline = pango_layout_get_baseline (layout) / PANGO_SCALE; - } - - if (widest->width < smallest->width) - { - *smallest = *widest; - *smallest_baseline = *widest_baseline; + layout = gtk_label_get_measuring_layout (self, layout, 0); + pango_layout_get_size (layout, minimum, NULL); + /* yes, Pango ellipsizes even when that needs more space */ + *minimum = MIN (*minimum, *natural); } + else + *minimum = *natural; + + get_default_widths (self, &minimum_default, &natural_default); + if (minimum_default > *minimum) + *minimum = minimum_default; + if (natural_default > -1) + *natural = natural_default; + *natural = MAX (*minimum, *natural); } else { - *smallest = *widest; - *smallest_baseline = *widest_baseline; + pango_layout_get_size (layout, NULL, minimum); + *minimum_baseline = pango_layout_get_baseline (layout); + + *natural = *minimum; + *natural_baseline = *minimum_baseline; } g_object_unref (layout); } static void -gtk_label_get_preferred_size (GtkWidget *widget, - GtkOrientation orientation, - int *minimum_size, - int *natural_size, - int *minimum_baseline, - int *natural_baseline) +get_height_for_width (GtkLabel *self, + int width, + int *minimum_height, + int *natural_height, + int *minimum_baseline, + int *natural_baseline) { - GtkLabel *self = GTK_LABEL (widget); - PangoRectangle widest_rect; - PangoRectangle smallest_rect; - int smallest_baseline; - int widest_baseline; + PangoLayout *layout; + int natural_width, text_height, baseline; - gtk_label_get_preferred_layout_size (self, - &smallest_rect, &widest_rect, - &smallest_baseline, &widest_baseline); + if (width < 0) + { + /* Minimum height is assuming infinite width */ + layout = gtk_label_get_measuring_layout (self, NULL, -1); + pango_layout_get_size (layout, NULL, minimum_height); + baseline = pango_layout_get_baseline (layout); + *minimum_baseline = baseline; + + /* Natural height is assuming natural width */ + get_default_widths (self, NULL, &natural_width); + + layout = gtk_label_get_measuring_layout (self, layout, natural_width); + pango_layout_get_size (layout, NULL, natural_height); + baseline = pango_layout_get_baseline (layout); + *natural_baseline = baseline; + } + else + { + /* minimum = natural for any given width */ + layout = gtk_label_get_measuring_layout (self, NULL, width); - widest_rect.width = PANGO_PIXELS_CEIL (widest_rect.width); - widest_rect.height = PANGO_PIXELS_CEIL (widest_rect.height); + pango_layout_get_size (layout, NULL, &text_height); - smallest_rect.width = PANGO_PIXELS_CEIL (smallest_rect.width); - smallest_rect.height = PANGO_PIXELS_CEIL (smallest_rect.height); + *minimum_height = text_height; + *natural_height = text_height; - if (orientation == GTK_ORIENTATION_HORIZONTAL) + baseline = pango_layout_get_baseline (layout); + *minimum_baseline = baseline; + *natural_baseline = baseline; + } + + g_object_unref (layout); +} + +static void +get_width_for_height (GtkLabel *self, + int height, + int *minimum_width, + int *natural_width) +{ + PangoLayout *layout; + int minimum_default, natural_default; + + get_default_widths (self, &minimum_default, &natural_default); + + if (height < 0) { - /* Normal desired width */ - *minimum_size = smallest_rect.width; - *natural_size = widest_rect.width; + /* Minimum width is as many line breaks as possible */ + layout = gtk_label_get_measuring_layout (self, NULL, MAX (minimum_default, 0)); + pango_layout_get_size (layout, minimum_width, NULL); + + /* Natural width is natural width - or as wide as possible */ + layout = gtk_label_get_measuring_layout (self, layout, natural_default); + pango_layout_get_size (layout, natural_width, NULL); + *natural_width = MAX (*natural_width, natural_default); + *natural_width = MAX (*natural_width, *minimum_width); } - else /* GTK_ORIENTATION_VERTICAL */ + else { - if (smallest_rect.height < widest_rect.height) - { - *minimum_size = smallest_rect.height; - *natural_size = widest_rect.height; - *minimum_baseline = smallest_baseline; - *natural_baseline = widest_baseline; - } - else + int min, max, mid, text_width, text_height; + + /* binary search for the smallest width where the height doesn't + * eclipse the given height */ + min = MAX (minimum_default, 0); + layout = gtk_label_get_measuring_layout (self, NULL, -1); + pango_layout_get_size (layout, &max, NULL); + + while (min < max) { - *minimum_size = widest_rect.height; - *natural_size = smallest_rect.height; - *minimum_baseline = widest_baseline; - *natural_baseline = smallest_baseline; + mid = (min + max) / 2; + layout = gtk_label_get_measuring_layout (self, layout, mid); + pango_layout_get_size (layout, &text_width, &text_height); + if (text_width > mid) + min = mid = text_width; + if (text_height > height) + min = mid + 1; + else + max = text_width; } + + *minimum_width = min; + *natural_width = min; } -} + g_object_unref (layout); +} static void gtk_label_measure (GtkWidget *widget, @@ -1200,14 +1224,22 @@ gtk_label_measure (GtkWidget *widget, { GtkLabel *self = GTK_LABEL (widget); - if (orientation == GTK_ORIENTATION_VERTICAL && for_size != -1 && self->wrap) - { - gtk_label_clear_layout (self); + if (for_size > 0) + for_size *= PANGO_SCALE; - get_height_for_width (self, for_size, minimum, natural, minimum_baseline, natural_baseline); - } + if (!self->wrap) + get_static_size (self, orientation, minimum, natural, minimum_baseline, natural_baseline); + else if (orientation == GTK_ORIENTATION_VERTICAL) + get_height_for_width (self, for_size, minimum, natural, minimum_baseline, natural_baseline); else - gtk_label_get_preferred_size (widget, orientation, minimum, natural, minimum_baseline, natural_baseline); + get_width_for_height (self, for_size, minimum, natural); + + *minimum = PANGO_PIXELS_CEIL (*minimum); + *natural = PANGO_PIXELS_CEIL (*natural); + if (*minimum_baseline > 0) + *minimum_baseline = PANGO_PIXELS_CEIL (*minimum_baseline); + if (*natural_baseline > 0) + *natural_baseline = PANGO_PIXELS_CEIL (*natural_baseline); } static void diff --git a/testsuite/reftests/label-sizing.ref.ui b/testsuite/reftests/label-sizing.ref.ui index 1616ef4f2f..4235424e80 100644 --- a/testsuite/reftests/label-sizing.ref.ui +++ b/testsuite/reftests/label-sizing.ref.ui @@ -212,8 +212,7 @@ end end 0 - ABCDE -ABCD + ABCDE ABCD @@ -224,8 +223,7 @@ ABCD end start 0 - ABCDE -ABCD + ABCDE ABCD @@ -646,8 +644,7 @@ ABCD end end 0 - ABCDE -ABCD + ABCDE ABCD 4 @@ -659,8 +656,7 @@ ABCD end start 0 - ABCDE -ABCD + ABCDE ABCD 4 @@ -1092,8 +1088,7 @@ ABCD end end 0 - ABCDE -ABCD + ABCDE ABCD 8 @@ -1105,8 +1100,7 @@ ABCD end start 0 - ABCDE -ABCD + ABCDE ABCD 8 @@ -1982,8 +1976,7 @@ ABCD end end 0 - ABCDE -ABCD + ABCDE ABCD 4 @@ -1995,8 +1988,7 @@ ABCD end start 0 - ABCDE -ABCD + ABCDE ABCD 4 @@ -2016,8 +2008,7 @@ ABCD start end 0 - ABCDE -ABCD + ABCDE ABCD 4 @@ -2029,8 +2020,7 @@ ABCD start start 0 - ABCDE -ABCD + ABCDE ABCD 4 @@ -2434,8 +2424,7 @@ ABCD end end 0 - ABCDE -ABCD + ABCDE ABCD 4 4 @@ -2448,8 +2437,7 @@ ABCD end start 0 - ABCDE -ABCD + ABCDE ABCD 4 4 @@ -2470,8 +2458,7 @@ ABCD start end 0 - ABCDE -ABCD + ABCDE ABCD 4 4 @@ -2484,8 +2471,7 @@ ABCD start start 0 - ABCDE -ABCD + ABCDE ABCD 4 4 @@ -2898,8 +2884,7 @@ ABCD end end 0 - ABCDE -ABCD + ABCDE ABCD 8 4 @@ -2912,8 +2897,7 @@ ABCD end start 0 - ABCDE -ABCD + ABCDE ABCD 8 4 @@ -2934,8 +2918,7 @@ ABCD start end 0 - ABCDE -ABCD + ABCDE ABCD 8 4 @@ -2948,8 +2931,7 @@ ABCD start start 0 - ABCDE -ABCD + ABCDE ABCD 8 4 @@ -3818,8 +3800,7 @@ ABCD end end 0 - ABCDE -ABCD + ABCDE ABCD 8 @@ -3831,8 +3812,7 @@ ABCD end start 0 - ABCDE -ABCD + ABCDE ABCD 8 @@ -3852,8 +3832,7 @@ ABCD start end 0 - ABCDE -ABCD + ABCDE ABCD 8 @@ -3865,8 +3844,7 @@ ABCD start start 0 - ABCDE -ABCD + ABCDE ABCD 8 @@ -4270,8 +4248,7 @@ ABCD end end 0 - ABCDE -ABCD + ABCDE ABCD 4 8 @@ -4284,8 +4261,7 @@ ABCD end start 0 - ABCDE -ABCD + ABCDE ABCD 4 8 @@ -4306,8 +4282,7 @@ ABCD start end 0 - ABCDE -ABCD + ABCDE ABCD 4 8 @@ -4320,8 +4295,7 @@ ABCD start start 0 - ABCDE -ABCD + ABCDE ABCD 4 8 @@ -4734,8 +4708,7 @@ ABCD end end 0 - ABCDE -ABCD + ABCDE ABCD 8 8 @@ -4748,8 +4721,7 @@ ABCD end start 0 - ABCDE -ABCD + ABCDE ABCD 8 8 @@ -4770,8 +4742,7 @@ ABCD start end 0 - ABCDE -ABCD + ABCDE ABCD 8 8 @@ -4784,8 +4755,7 @@ ABCD start start 0 - ABCDE -ABCD + ABCDE ABCD 8 8 @@ -5654,8 +5624,7 @@ ABCD end end 0 - ABCDE -ABCD + ABCDE ABCD 12 @@ -5667,8 +5636,7 @@ ABCD end start 0 - ABCDE -ABCD + ABCDE ABCD 12 @@ -6104,8 +6072,7 @@ ABCD end end 0 - ABCDE -ABCD + ABCDE ABCD 4 12 @@ -6118,8 +6085,7 @@ ABCD end start 0 - ABCDE -ABCD + ABCDE ABCD 4 12 @@ -6566,8 +6532,7 @@ ABCD end end 0 - ABCDE -ABCD + ABCDE ABCD 8 12 @@ -6580,8 +6545,7 @@ ABCD end start 0 - ABCDE -ABCD + ABCDE ABCD 8 12 -- 2.30.2